package com.danicsoft.daide.token;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * The token class is the key class for interacting with information sent from
 * the server. It has several subclasses, each for a different type of token
 * sent from the server. These subclasses are: {@link StandardToken},
 * {@link IntegerToken}, {@link TextToken} and {@link TokenGroup}.
 * 
 * @author Daniel Yule (daniel.yule@gmail.com)
 * @version 1.0
 */
public abstract class Token {
	/**
	 * The type of this token. For a standard token this is determined by the
	 * first byte of the token, for all others it is an implementation dependant
	 * constant found in <code>TokenMap</code>.
	 */
	private byte type;

	/**
	 * Construct a token of the given type. Since every token must have a type,
	 * this is the only constructor.
	 * 
	 * @param type
	 *        The type of this token.
	 */
	protected Token(byte type) {
		this.type = type;
	}

	/**
	 * Gets the type of this token.
	 * 
	 * @return This token's type.
	 */
	public byte getType() {
		return type;
	}

	/**
	 * Gets the data aspect of this token. Each subclass returns a different
	 * type of data, so it is recommeneded to look at their documentation for
	 * more information.
	 * 
	 * @return An object containing this object's data.
	 */
	public abstract Object getData();

	/**
	 * Reads a token from the input stream and returns it.
	 * 
	 * @param iStream
	 *        The <code>InputStream</code> to load from.
	 * @param mapping
	 *        The <code>TokenMap</code> to use to decipher this token.
	 * @return A subclass of <code>Token</code> that represents this token.
	 * @throws IOException
	 *         If there was a problem reading from the input stream.
	 */
	public static Token readToken(InputStream iStream, TokenMap mapping) throws IOException {
		byte firstByte;

		firstByte = (byte) iStream.read();

		if (firstByte == TokenMap.TEXT) {

			StringBuilder builder = new StringBuilder();
			while (firstByte == TokenMap.TEXT) {
				char character = (char) iStream.read();
				builder.append(character);
				iStream.mark(2);
				firstByte = (byte) iStream.read();
			}
			iStream.reset();
			return new TextToken(builder.toString());
		}
		else {

			if ((firstByte & 0xC0) == 0) {

				byte secondByte = (byte) iStream.read();

				return new IntegerToken(firstByte, secondByte);
			}
			else {
				byte secondByte = (byte) iStream.read();
				return new StandardToken(firstByte, secondByte, mapping);
			}
		}
	}

	@Override
	public abstract Token clone();

	@Override
	public abstract String toString();

	@Override
	public abstract int hashCode();

	/**
	 * Outputs this token to the output stream as a stream of bytes.
	 * 
	 * @param output
	 *        The <code>OutputStream</code> to output to.
	 * @throws IOException
	 *         If there was a problem writing to the output stream.
	 */
	public abstract void output(OutputStream output) throws IOException;

	/**
	 * Returns true if this is a group type (removes the need for an instanceof
	 * check)
	 * 
	 * @return If this is a group type.
	 */
	public boolean isGroup() {
		return false;
	}

	@Override
	public final boolean equals(Object obj) {
		return obj.hashCode() == hashCode();
	}

}
